#!/Library/Frameworks/Python.framework/Versions/3.4/bin/python3
# -*- coding: utf-8 -*-
import os
import sys
import re
import subprocess
import socket
import tkinter as tk
import tkinter.font as tkFont
import tkinter.ttk as ttk
import threadpool
import xml.dom.minidom
#import tkinter.messagebox
from tkinter.messagebox import *

import threading

__author__ = 'DURD'

class ScrolledText(tk.Frame):
    def __init__(self, parent=None, text='', file=None):
        tk.Frame.__init__(self, parent)
        self.pack(expand=tk.YES, fill=tk.BOTH) # make me expandable
        self.makewidgets()
        self.settext(text, file)

    def makewidgets(self):
        sbar = ttk.Scrollbar(self)
        text = tk.Text(self, relief=tk.SUNKEN)
        text['state'] = 'disabled'
        sbar.config(command=text.yview) # xlink sbar and text
        text.config(yscrollcommand=sbar.set) # move one moves other
        sbar.pack(side=tk.RIGHT, fill=tk.Y) # pack first=clip last
        text.pack(side=tk.LEFT, expand=tk.YES, fill=tk.BOTH) # text clipped first
        self.text = text

    def settext(self, text='', file=None):
        self.text['state'] = 'normal'
        if file:
            text = open(file, 'r').read()
        self.text.delete('1.0', tk.END) # delete current text
        self.text.insert('1.0', text) # add at line 1, col 0
        self.text.mark_set(tk.INSERT, '1.0') # set insert cursor
        self.text.focus() # save user a click
        self.text['state'] = 'disabled'

    def gettext(self): # returns a string
        return self.text.get('1.0', tk.END+'-1c') # first through last

class McListBox():
    """use a ttk.TreeView as a multicolumn ListBox"""

    def __init__(self,table_header = None,table_list = None, frame=None, app=None):
        self.frame = frame
        self.table_header = table_header
        self.table_list = table_list
        self.tree = None
        self.app = app
        #self.action = app.action
        self._setup_widgets()
        self._build_tree()

    def _setup_widgets(self):
        self.container = ttk.Frame(self.frame)
        self.container.pack(side=tk.TOP, fill=tk.Y, expand=True)
        # create a treeview with dual scrollbars
        self.tree = ttk.Treeview(columns=self.table_header, show="headings")
        self.tree.bind('<<TreeviewSelect>>', self.on_click)
        self.tree.bind("<Double-1>", self.on_double_click)
        #self.tree.bind("<3>", self.on_double_click)
        #self.tree.
        self.vsb = ttk.Scrollbar(orient="vertical",
            command=self.tree.yview)
        self.hsb = ttk.Scrollbar(orient="horizontal",
            command=self.tree.xview)
        self.tree.configure(yscrollcommand=self.vsb.set,
            xscrollcommand=self.hsb.set)
        self.tree.grid(column=0, row=0, sticky='nsew', in_=self.container)
        self.vsb.grid(column=1, row=0, sticky='ns', in_=self.container)
        self.hsb.grid(column=0, row=1, sticky='ew', in_=self.container)

        self.container.grid_columnconfigure(0, weight=1)
        self.container.grid_rowconfigure(0, weight=1)

    def _build_tree(self):
        for col in self.table_header:
            self.tree.heading(col, text=col.title(),
                command=lambda c=col: sortby(self.tree, c, 0))
            # adjust the column's width to the header string
            self.tree.column(col,
                width=tkFont.Font().measure(col.title()))

        for item in self.table_list:
            temp = [item.index, item.monitored, item.ip, '']
            if temp[1]:
                temp[1] = '◆'
            else:
                temp[1] = '◇'
            print(temp)
            self.tree.insert('', 'end', str(temp[0]), values=temp)
            # adjust column's width if necessary to fit each value
            for ix, val in enumerate(temp):
                col_w = tkFont.Font().measure(val)
                if self.tree.column(self.table_header[ix],width=None)<col_w:
                    self.tree.column(self.table_header[ix], width=col_w)

    def _rebuild_tree(self):
        #self.tree.configure(columns=self.table_header)
        self._build_tree()

    def _set_header(self,header = None):
        self.table_header = header

    def _set_list(self,list = None):
        self.table_list = list

    def set_ret_status(self, host, ret_str):
        for index in range(len(self.table_list)):
            if self.table_list[index].ip == host.ip:
                temp = [self.table_list[index].index, self.table_list[index].monitored, self.table_list[index].ip, ret_str]
                item_id = index + 1
                if temp[1]:
                    temp[1] = '◆'
                else:
                    temp[1] = '◇'
                self.tree.item(item_id, values=temp)
                break

    def on_click(self, event):
        global pool
        item_id = int(self.tree.focus())
        requests = threadpool.makeRequests(self.RunCommandAndSetText, [item_id - 1])
        [pool.putRequest(req) for req in requests]
        #result = self.table_list[item_id - 1].RunCommand('cat /mnt/nand/info.cfg')
        #app.st.settext(result)

    def RunCommandAndSetText(self, index):
        result = self.table_list[index].RunCommand('cat /mnt/nand/info.cfg')
        #result = self.table_list[index].RunCommand('cat ~/Documents/project/python/bulk_config.py')
        app.st.settext(result)

    def on_double_click(self, event):
        print('Here')
        item_id = int(self.tree.focus())
        temp = [self.table_list[item_id - 1].index, self.table_list[item_id - 1].monitored, self.table_list[item_id - 1].ip]
        if temp[1]:
            self.table_list[item_id - 1].monitored = False
            temp[1] = '◇'
        else:
            self.table_list[item_id - 1].monitored = True
            temp[1] = '◆'
        self.tree.item(item_id, values=temp)


def sortby(tree, col, descending):
    """sort tree contents when a column header is clicked on"""
    # grab values to sort
    data = [(tree.set(child, col), child) \
        for child in tree.get_children('')]
    # if the data to be sorted is numeric change to float
    #data =  change_numeric(data)
    # now sort the data in place
    data.sort(reverse=descending)
    for ix, item in enumerate(data):
        tree.move(item[1], '', ix)
    # switch the heading so it will sort in the opposite direction
    tree.heading(col, command=lambda col=col: sortby(tree, col, \
        int(not descending)))

class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.pack(fill='both', expand='true')
        self.createFrame()

    def createFrame(self):
        self.bframe = tk.Frame(self)
        self.bframe.pack(side=tk.BOTTOM,fill=tk.X, expand='false')

        tk.Label(self.bframe, text='').pack(side='top')
        tk.Label(self.bframe, text='').pack(side='bottom')
        tk.Label(self.bframe, text='     ').pack(side='right')

        self.lbAction = tk.Label(self.bframe, text='请选择行为    ').pack(side=tk.LEFT, fill='none')
        self.Action = tk.StringVar(self.bframe)
        self.Action.set("执行预定义脚本")
        self.cbox = ttk.Combobox(self.bframe, textvariable=self.Action, state='readonly')
        self.cbox['values'] = ('执行预定义脚本', "LED灯测试", "批量部署", "执行自定义脚本", "性能测试")
        self.cbox.current(0)
        self.cbox.pack(side=tk.LEFT, fill='both', expand='false')
        btnOK = tk.Button(self.bframe, text='执行命令', command=self.runCommand).pack(side=tk.RIGHT)


        self.header = [' 编号 ', ' 运行 ', ' IP地址 ', ' 状态 ']
        self.ipinfo_list = self.ScanHost()
        self.lframe = tk.Frame(self)
        self.lframe.pack(side=tk.LEFT, anchor='w', fill=tk.Y, expand='false')
        self.mc_listbox = McListBox(table_header=self.header,table_list=self.ipinfo_list, frame=self.lframe, app=self)
        tk.Button(self.lframe, text="自动搜索设备").pack(side=tk.LEFT)
        tk.Button(self.lframe, text="删除设备").pack(side=tk.RIGHT)
        tk.Button(self.lframe, text="添加设备").pack(side=tk.RIGHT)


        self.rframe = tk.Frame(self)
        self.rframe.pack(side=tk.LEFT, anchor='w', fill='both', expand='true')
        self.tb = ttk.Notebook(self.rframe)
        self.st1 = ScrolledText(text='')
        self.st2 = ScrolledText(text='')

        self.cmd_frame = tk.Frame(self)
        self.st3 = ScrolledText(self.cmd_frame, text='Hello')
        self.st31 = ttk.Label(self.cmd_frame)
        self.st32 = ttk.Entry(self.cmd_frame)
        self.st33 = ttk.Button(self.cmd_frame, text='RUN')
        self.st3.pack(side='top')
        self.st31.pack(side='top', expand=True, fill='both')
        self.st32.pack(side='left', expand=True, fill='both')
        self.st33.pack(side='left')
        self.tb.add(self.st1, text="设备信息")
        self.tb.add(self.st2, text="执行命令详细信息")
        self.tb.add(self.cmd_frame, text="命令交互")
        self.tb.pack(fill='both', expand='true', side='left')

    def ScanHost(self, auto=False):
        addr_list = []
        output_list = []
        if auto:
            host=''
            port=7000
            send_addr = ('<broadcast>',7000)
            s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
            s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
            s.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
            s.bind((host,port))
            s.sendto("<?xml version=\"1.0\" encoding=\"UTF-8\"?>",send_addr)
            while True:
                try:
                    data,addr=s.recvfrom(4096)
                    dom = xml.dom.minidom.parse(data)
                    root = dom.documentElement
                    IpElement = root.getElementsByTagName('IPv4')
                    Ip = IpElement[0].firstChild.data
                    print(IpElement[0].firstChild.data)
                    output_list.append(Ip)
                except:
                    raise
            #output = subprocess.check_output(['arp-scan', '-l'])
            #output_str = str(output, encoding='utf-8')
            #output_list = output_str.split('\n')

            # find out the line start with an ip address.
            regex = re.compile(r'^\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b.*', re.IGNORECASE)

            index = 1
            for line in output_list:
                if re.match(regex, line):
                    #addr_list[(line.split()[0])] = True
                    tempHost = Host(line.split()[0])
                    tempHost.index = index
                    tempHost.monitored = True
                    addr_list.append(tempHost)
                    index += 1
    #        print(addr_list)
        else:
            with open(r'./hosts.txt') as host_file:
                index = 1
                for line in host_file.readlines():
                    regex = re.compile(r'^\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b$', re.IGNORECASE)
                    if re.match(regex, line):
                        #addr_list[(line.strip())] = True
                        tempHost = Host(line.split()[0])
                        tempHost.index = index
                        tempHost.monitored = True
                        addr_list.append(tempHost)
                        index += 1
    #            print(addr_list)

        return addr_list

    def runCommand(self):
        def RunCommandAndSetTextBulk(host):
            print(host)
            ret, output = host.RunCommandBulk(self.script)
            with open(r'./log/127.0.0.1.log', 'a') as log_fd:
                log_fd.write(str(output, encoding='utf-8'))

            print(ret)
            if ret != 0:
                self.mc_listbox.set_ret_status(host, 'FAIL')
            else:
                self.mc_listbox.set_ret_status(host, 'OK')
            self.st2.settext(output)

        def BulkConfig(host):
            print(host)
            ret, output = host.Cp2Remote('./data' + self.bin_list[0], '/tmp')
            with open(r'./log/127.0.0.1.log', 'a') as log_fd:
                log_fd.write(str(output, encoding='utf-8'))

            ret, output = host.RunCommand('sh' + '/tmp/' + self.bin_list[0])
            with open(r'./log/127.0.0.1.log', 'a') as log_fd:
                log_fd.write(str(output, encoding='utf-8'))

            print(ret)
            if ret != 0:
                self.mc_listbox.set_ret_status(host, 'FAIL')
            else:
                self.mc_listbox.set_ret_status(host, 'OK')
            self.st2.settext(output)

        global pool
        action = self.Action.get()
        from tkinter.filedialog import askopenfilename
        #"LED灯测试", "批量部署", "执行自定义脚本", "性能测试"
        if action == "执行自定义脚本":
            self.script = askopenfilename()
            if sys.platform == 'win32':
                self.script = self.script.replace('/', '\\')
        if action == '执行预定义脚本':
            self.script = 'data/config.sh'
        if action == "执行自定义脚本" or action == '执行预定义脚本':
            avialbe_ip_list = []
            for item in self.ipinfo_list:
                if item.monitored:
                    avialbe_ip_list.append(item)
                    print(item.ip)
            print(avialbe_ip_list)
            requests = threadpool.makeRequests(RunCommandAndSetTextBulk, avialbe_ip_list)
            [pool.putRequest(req) for req in requests]
        if action == "批量部署":
            file_list = os.listdir('./data')
            self.bin_list = []
            for item in file_list:
                if os.path.splitext(l)[1] == '.bin':
                     self.bin_list.append(item)
            if len(bin_list) > 1:
                showerror("Bulk Config Error", "More than one binary in data dir, please confirm it.")
                print("More than one binary is in dir, please confirm it.")
            else:
                avialbe_ip_list = []
                for item in self.ipinfo_list:
                    if item.monitored:
                        avialbe_ip_list.append(item)
                        print(item.ip)
                print(avialbe_ip_list)
                requests = threadpool.makeRequests(BulkConfig, avialbe_ip_list)
                [pool.putRequest(req) for req in requests]
            #deploy_bin =



class Host():
    def __init__(self, ip):
        self.monitored = True
        self.index = 0
        self.ip = ip
        self.username = 'root'
        self.passwd = ''
        self.ssh_enable = None
        self.initialized = False
        self.logfile = './log/' + ip + '.log'

    def init(self):
        try:
            if sys.platform == 'win32':
                self.ssh = r'bin\plink.exe -ssh -C -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'
                self.telnet = r'bin\plink.exe -telnet'
                self.scp = r'bin\pscp.exe -scp'
                self.ssh_key = r'etc\id_rsa'
            else:
                self.ssh = 'ssh -C -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'
                self.telnet = 'telnet'
                self.scp = 'scp'
                self.ssh_key = r'/etc/id_rsa'

            # check if ssh is usable
            import socket
            sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sk.settimeout(1)
            try:
                sk.connect((self.ip,22))
                self.ssh_enable = True
            except Exception:
                self.ssh_enable = False
            sk.close()

        except :
            print('Error.')

    def RunCommand(self, cmd):
        if not self.initialized:
            self.init()

        remote_cmd = ''
        if self.ssh_enable:
            #remote_cmd = self.ssh + ' -i ' + self.ssh_key + ' ' + self.username + '@' + self.ip + ' ' + '\"' + cmd + '\"'
            remote_cmd = 'ssh durd@127.0.0.1 ' + '\"' + cmd + '\"'
        else:
            # TODO
            pass
        print(remote_cmd)
        ret = 0
        output = ''
        try:
            output = subprocess.check_output(remote_cmd, stderr=subprocess.STDOUT, shell=True)
        except subprocess.CalledProcessError as ex:
            ret = ex.returncode
            output = ex.output

        print(output)
        return ret, output

    def RunCommandBulk(self, cmdfile):
        print('RunCommandBulk')
        if not self.initialized:
            self.init()

        self.Cp2Remote(cmdfile, '/tmp')
        return self.RunCommand('/tmp/' + os.path.basename(cmdfile))

    def Cp2Remote(self, src, dest):
        if not self.initialized:
            self.init()

        remote_cmd = ''
        if self.ssh_enable:

            #remote_cmd = self.scp + ' -i ' + self.ssh_key + ' ' + src + ' ' + self.username + '@' + self.ip + ':' + dest
            remote_cmd = 'scp ./data/config.sh durd@127.0.0.1:/tmp'
        else:
            # TODO
            pass
        print(remote_cmd)

        ret = 0
        output = ''
        try:
            output = subprocess.check_output(remote_cmd, stderr=subprocess.STDOUT, shell=True)
        except subprocess.CalledProcessError as ex:
            ret = ex.returncode
            output = ex.output

        print(output)
        return ret, output


    def Cp2Local(self, src, dest):
        if not self.initialized:
            self.init()

        remote_cmd = ''
        if self.ssh_enable:
            remote_cmd = self.scp + ' -i ' + self.ssh_key  + ' ' + self.username + '@' + self.ip + ':' + dest + ' ' + src
        else:
            # TODO
            pass
        return subprocess.check_output(remote_cmd, stderr=subprocess.STDOUT, shell=True)

if __name__ == '__main__':
    global pool
    pool = threadpool.ThreadPool(10)
    root = tk.Tk()
    root.wm_title("BITCOM 批量部署工具")
    app = Application(master=root)
    app.mainloop()

